/*-------------------------------------------------------------------------
 *
 * xlogreader.h
 *		通用 XLog 读取设施的定义
 *
 * Portions Copyright (c) 2013-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *		src/include/access/xlogreader.h
 *
 * NOTES
 *		请参阅 XLogReaderState 结构的定义以获取如何使用
 *		XLogReader 基础设施的说明。
 *
 *		基本思想是通过 XLogReaderAllocate() 分配一个 XLogReaderState，
 *		使用 XLogBeginRead() 或 XLogFindNextRecord() 将读取器定位到第一个记录，
 *		并调用 XLogReadRecord() 直到它返回 NULL。
 *
 *		调用者如果希望调用 XLogReadRecord 或 XLogFindNextRecord，
 *		则提供一个 page_read 回调；如果不是，则可以将其传递为 NULL。
 *		WALRead 函数可以用作编写 page_read 回调的辅助工具，但不是强制的；
 *		使用它的调用者必须提供 segment_open 回调。
 *		segment_close 回调必须始终提供。
 *
 *		通过调用 XLogReadRecord() 读取记录后，它会分解为
 *		每个块和主数据部分，可以通过 XLogRec* 宏和函数访问这些部分。
 *		您还可以通过调用 DecodeXLogRecord() 函数解码已经在内存中构造的记录，而无需从磁盘读取。
 *-------------------------------------------------------------------------
 */
#ifndef XLOGREADER_H
#define XLOGREADER_H

#ifndef FRONTEND
#include "access/transam.h"
#endif

#include "access/xlogrecord.h"
#include "storage/buf.h"

/* WALOpenSegment表示正在读取的WAL段。 */
typedef struct WALOpenSegment
{
	int			ws_file;		/* 段文件描述符 */
	XLogSegNo	ws_segno;		/* 段号 */
	TimeLineID	ws_tli;			/* 当前打开文件的时间线ID */
} WALOpenSegment;

/* WALSegmentContext携带有关要读取的WAL段的上下文信息 */
typedef struct WALSegmentContext
{
	char		ws_dir[MAXPGPATH];
	int			ws_segsize;
} WALSegmentContext;

typedef struct XLogReaderState XLogReaderState;

/* 不同xlogreader交互的函数类型定义 */
typedef int (*XLogPageReadCB) (XLogReaderState *xlogreader,
							   XLogRecPtr targetPagePtr,
							   int reqLen,
							   XLogRecPtr targetRecPtr,
							   char *readBuf);
typedef void (*WALSegmentOpenCB) (XLogReaderState *xlogreader,
								  XLogSegNo nextSegNo,
								  TimeLineID *tli_p);
typedef void (*WALSegmentCloseCB) (XLogReaderState *xlogreader);

typedef struct XLogReaderRoutine
{
	/*
	 * 数据输入回调
	 *
	 * 此回调应读取从targetPagePtr开始的至少reqLen个有效字节的xlog页，并将它们存储在readBuf中。
	 * 回调应返回读取的字节数（不超过XLOG_BLCKSZ），如果失败则返回-1。必要时，回调应暂停以等待
	 * 请求的字节可用。除非需要的字节数超过返回的字节数，否则不会再次调用相同页的回调。
	 *
	 * targetRecPtr是我们正在读取的WAL记录的位置。通常它等于targetPagePtr + reqLen，但有时xlogreader
	 * 需要读取并验证页或段头，然后再读取它所关心的实际WAL记录。在这种情况下，targetRecPtr可以
	 * 用于确定要从哪个时间线读取页面。
	 *
	 * 回调应将->seg.ws_tli设置为读取页面的文件的TLI。
	 */
	XLogPageReadCB page_read;

	/*
	 * 回调以打开指定的WAL段进行读取。->seg.ws_file
	 * 应设置为打开的段的文件描述符。如果失败，回调应引发错误，并且不得返回。
	 *
	 * "nextSegNo"是要打开的段的编号。
	 *
	 * "tli_p"是输入/输出参数。WALRead()以此传递新段应找到的时间线，但回调可以使用它返回
	 * 实际打开的TLI。
	 */
	WALSegmentOpenCB segment_open;

	/*
	 * WAL段关闭回调。->seg.ws_file应设置为负数。
	 */
	WALSegmentCloseCB segment_close;
} XLogReaderRoutine;

#define XL_ROUTINE(...) &(XLogReaderRoutine){__VA_ARGS__}

typedef struct
{
	/* 此块引用是否正在使用？ */
	bool		in_use;

	/* 确定此引用指向的块 */
	RelFileNode rnode;
	ForkNumber	forknum;
	BlockNumber blkno;

	/* 预取工作区。 */
	Buffer		prefetch_buffer;

	/* XLogRecordBlockHeader中fork_flags字段的副本 */
	uint8		flags;

	/* 有关全页图像的信息（如果有） */
	bool		has_image;		/* 有图像，即使是为了一致性检查 */
	bool		apply_image;	/* 有应被恢复的图像 */
	char	   *bkp_image;
	uint16		hole_offset;
	uint16		hole_length;
	uint16		bimg_len;
	uint8		bimg_info;

	/* 与此块关联的 rmgr 特定数据的缓冲区 */
	bool		has_data;
	char	   *data;
	uint16		data_len;
	uint16		data_bufsz;
} DecodedBkpBlock;

/*
 * 记录的解码内容。这占用一块连续的内存区域，
 * main_data和blocks[n].data指向此处声明的成员之后的内存。
 */
typedef struct DecodedXLogRecord
{
	/* 用于资源管理的私有成员。 */
	size_t		size;			/* 解码记录的总大小 */
	bool		oversized;		/* 超出常规解码缓冲区吗？ */
	struct DecodedXLogRecord *next; /* 解码记录队列链接 */

	/* 公共成员。 */
	XLogRecPtr	lsn;			/* location */
	XLogRecPtr	next_lsn;		/* 下一个记录的位置 */
	XLogRecord	header;			/* header */
	RepOriginId record_origin;
	TransactionId toplevel_xid; /* 顶级事务的XID */
	char	   *main_data;		/* 记录的主要数据部分 */
	uint32		main_data_len;	/* 主要数据部分的长度 */
	int			max_block_id;	/* 使用的最高block_id（如果没有则为-1） */
	DecodedBkpBlock blocks[FLEXIBLE_ARRAY_MEMBER];
} DecodedXLogRecord;

struct XLogReaderState
{
	/*
	 * 操作回调
	 */
	XLogReaderRoutine routine;

	/* ----------------------------------------
	 * 公共参数
	 * ----------------------------------------
	 */

	/*
	 * 我们即将读取的xlog文件的系统标识符。如果未知或不重要，则设置为零（默认值）。
	 */
	uint64		system_identifier;

	/*
	 * 回调使用的不透明数据。XLogReader未使用。
	 */
	void	   *private_data;

	/*
	 * 最后读取记录的起始和结束点。EndRecPtr也用作下一个读取的位置。调用XLogBeginRead()会将EndRecPtr
	 * 设置为起始位置，并将ReadRecPtr设置为无效。
	 *
	 * XLogReadRecord()返回的最后记录的起始和结束点。这些也可作为record->lsn和record->next_lsn访问。
	 */
	XLogRecPtr	ReadRecPtr;		/* 最后读取记录的开始 */
	XLogRecPtr	EndRecPtr;		/* end+1 of last record read */

	/*
	 * 在恢复结束时设置：WAL末尾部分记录的起始点
	 * （如果没有，则为InvalidXLogRecPtr），以及第一个缺失contra记录的起始位置。
	 */
	XLogRecPtr	abortedRecPtr;
	XLogRecPtr	missingContrecPtr;
	/* 当找到XLP_FIRST_IS_OVERWRITE_CONTRECORD时设置 */
	XLogRecPtr	overwrittenRecPtr;


	/* ----------------------------------------
	 * 当前记录的解码表示
	 *
	 * 使用XLogRecGet*函数来检查记录；这些字段
	 * 不应直接访问。
	 * ----------------------------------------
	 * 最后读取和解码的记录的起始和结束点
	 * 由XLogReadRecordInternal()提供。NextRecPtr也用于
	 * 解码下一个记录的位置。调用XLogBeginRead()将NextRecPtr和EndRecPtr设置为
	 * 请求的起始位置。
	 */
	XLogRecPtr	DecodeRecPtr;	/* 最后解码记录的开始 */
	XLogRecPtr	NextRecPtr;		/* 最后解码记录的结束+1 */
	XLogRecPtr	PrevRecPtr;		/* 上一个解码记录的开始 */

	/* XLogReadRecord()返回的最后记录。 */
	DecodedXLogRecord *record;

	/* ----------------------------------------
	 * 私有/内部状态
	 * ----------------------------------------
	 */

	/*
	 * 解码记录的缓冲区。这是一个循环缓冲区，
	 * 尽管单个记录无法在中间分割，因此在末尾常常会浪费一些空间。
	 * 超大记录不能适应此空间，因此单独分配。
	 */
	char	   *decode_buffer;
	size_t		decode_buffer_size;
	bool		free_decode_buffer; /* 需要释放吗？ */
	char	   *decode_buffer_head; /* 数据从头部读取 */
	char	   *decode_buffer_tail; /* 新数据在尾部写入 */

	/*
	 * 已解码记录的队列。这是一个链表，通常由
	 * decode_buffer中的连续记录组成，但也可能
	 * 包含使用palloc()分配的超大记录。
	 */
	DecodedXLogRecord *decode_queue_head;	/* 最旧的解码记录 */
	DecodedXLogRecord *decode_queue_tail;	/* 最新的解码记录 */

	/*
	 * 当前读取的页面的缓冲区（XLOG_BLCKSZ字节，至少有效到
	 * readLen字节）
	 */
	char	   *readBuf;
	uint32		readLen;

	/* 当前在readBuf中的数据的最后读取XLOG位置 */
	WALSegmentContext segcxt;
	WALOpenSegment seg;
	uint32		segoff;

	/*
	 * 先前页面读取的开始位置及其TLI。 不一定
	 * 与readBuf中的内容对应；用于时间线完整性检查。
	 */
	XLogRecPtr	latestPagePtr;
	TimeLineID	latestPageTLI;

	/* 正在读取的WAL记录的开始。 */
	XLogRecPtr	currRecPtr;
	/* 要从中读取的时间线，如果需要查找则为0 */
	TimeLineID	currTLI;

	/*
	 * 如果当前TLI是历史的，则在currTLI中读取的安全点
	 * （tliSwitchPoint）或在当前时间线上的InvalidXLogRecPtr。
	 *
	 * 实际上设置为包含结束currTLI有效性的时间线切换的段的开始，
	 * 而不是切换本身的LSN，因为我们不能假定旧段会存在。
	 */
	XLogRecPtr	currTLIValidUntil;

	/*
	 * 如果currTLI不是最新已知时间线，则在达到currTLIValidUntil时，
	 * 下一条要读取的时间线。
	 */
	TimeLineID	nextTLI;

	/*
	 * 当前ReadRecord结果的缓冲区（可扩展），
	 * 用于记录跨越页面边界时。
	 */
	char	   *readRecordBuf;
	uint32		readRecordBufSize;

	/* 用于保存错误消息的缓冲区 */
	char	   *errormsg_buf;
	bool		errormsg_deferred;

	/*
	 * 标志指示 XLogPageReadCB 不应阻塞等待数据。
	 */
	bool		nonblocking;
};

/*
 * 检查XLogNextRecord()是否还有更多排队的记录或需要返回的错误。
 */
static inline bool
XLogReaderHasQueuedRecordOrError(XLogReaderState *state)
{
	return (state->decode_queue_head != NULL) || state->errormsg_deferred;
}

/* 获取一个新的XLogReader */
extern XLogReaderState *XLogReaderAllocate(int wal_segment_size,
										   const char *waldir,
										   XLogReaderRoutine *routine,
										   void *private_data);
extern XLogReaderRoutine *LocalXLogReaderRoutine(void);

/* 释放一个 XLogReader */
extern void XLogReaderFree(XLogReaderState *state);

/* 可选地提供一个循环解码缓冲区以允许预读。 */
extern void XLogReaderSetDecodeBuffer(XLogReaderState *state,
									  void *buffer,
									  size_t size);

/* 将XLogReader定位到给定记录 */
extern void XLogBeginRead(XLogReaderState *state, XLogRecPtr RecPtr);
extern XLogRecPtr XLogFindNextRecord(XLogReaderState *state, XLogRecPtr RecPtr);

/* 从XLogPageReadCB返回值。 */
typedef enum XLogPageReadResult
{
	XLREAD_SUCCESS = 0,			/* 记录成功读取 */
	XLREAD_FAIL = -1,			/* 读取记录时失败 */
	XLREAD_WOULDBLOCK = -2		/* 仅非阻塞模式，没有数据 */
} XLogPageReadResult;

/* 读取下一个XLog记录。在WAL结束或失败时返回NULL */
extern struct XLogRecord *XLogReadRecord(XLogReaderState *state,
										 char **errormsg);

/* 消耗下一个记录或错误。 */
extern DecodedXLogRecord *XLogNextRecord(XLogReaderState *state,
										 char **errormsg);

/* 如果需要，释放先前返回的记录。 */
extern XLogRecPtr XLogReleasePreviousRecord(XLogReaderState *state);

/* 尝试提前读取，如果有数据和空间。 */
extern DecodedXLogRecord *XLogReadAhead(XLogReaderState *state,
										bool nonblocking);

/* 验证页面 */
extern bool XLogReaderValidatePageHeader(XLogReaderState *state,
										 XLogRecPtr recptr, char *phdr);

/* 忘记由XLogReaderValidatePageHeader()产生的错误。 */
extern void XLogReaderResetError(XLogReaderState *state);

/*
 * 来自WALRead的错误信息，后端和前端调用者都可以处理。
 * 目前仅可报告来自pg_pread的错误。
 */
typedef struct WALReadError
{
	int			wre_errno;		/* 上次pg_pread()设置的errno */
	int			wre_off;		/* 我们尝试读取的偏移量。 */
	int			wre_req;		/* 请求读取的字节数。 */
	int			wre_read;		/* 上次read()读取的字节数。 */
	WALOpenSegment wre_seg;		/* 我们尝试读取的段。 */
} WALReadError;

extern bool WALRead(XLogReaderState *state,
					char *buf, XLogRecPtr startptr, Size count,
					TimeLineID tli, WALReadError *errinfo);

/* 解码XLogRecord的函数 */

extern size_t DecodeXLogRecordRequiredSpace(size_t xl_tot_len);
extern bool DecodeXLogRecord(XLogReaderState *state,
							 DecodedXLogRecord *decoded,
							 XLogRecord *record,
							 XLogRecPtr lsn,
							 char **errmsg);

/*
 * 提供对最近由XLogReadRecord()或XLogNextRecord()返回的记录部分的访问的宏。
 */
#define XLogRecGetTotalLen(decoder) ((decoder)->record->header.xl_tot_len)
#define XLogRecGetPrev(decoder) ((decoder)->record->header.xl_prev)
#define XLogRecGetInfo(decoder) ((decoder)->record->header.xl_info)
#define XLogRecGetRmid(decoder) ((decoder)->record->header.xl_rmid)
#define XLogRecGetXid(decoder) ((decoder)->record->header.xl_xid)
#define XLogRecGetOrigin(decoder) ((decoder)->record->record_origin)
#define XLogRecGetTopXid(decoder) ((decoder)->record->toplevel_xid)
#define XLogRecGetData(decoder) ((decoder)->record->main_data)
#define XLogRecGetDataLen(decoder) ((decoder)->record->main_data_len)
#define XLogRecHasAnyBlockRefs(decoder) ((decoder)->record->max_block_id >= 0)
#define XLogRecMaxBlockId(decoder) ((decoder)->record->max_block_id)
#define XLogRecGetBlock(decoder, i) (&(decoder)->record->blocks[(i)])
#define XLogRecHasBlockRef(decoder, block_id)			\
	(((decoder)->record->max_block_id >= (block_id)) &&	\
	 ((decoder)->record->blocks[block_id].in_use))
#define XLogRecHasBlockImage(decoder, block_id)		\
	((decoder)->record->blocks[block_id].has_image)
#define XLogRecBlockImageApply(decoder, block_id)		\
	((decoder)->record->blocks[block_id].apply_image)

#ifndef FRONTEND
extern FullTransactionId XLogRecGetFullXid(XLogReaderState *record);
#endif

extern bool RestoreBlockImage(XLogReaderState *record, uint8 block_id, char *page);
extern char *XLogRecGetBlockData(XLogReaderState *record, uint8 block_id, Size *len);
extern void XLogRecGetBlockTag(XLogReaderState *record, uint8 block_id,
							   RelFileNode *rnode, ForkNumber *forknum,
							   BlockNumber *blknum);
extern bool XLogRecGetBlockTagExtended(XLogReaderState *record, uint8 block_id,
									   RelFileNode *rnode, ForkNumber *forknum,
									   BlockNumber *blknum,
									   Buffer *prefetch_buffer);

#endif							/* XLOGREADER_H */
